package cn.scw.framework.protocol.tcp;

import cn.scw.common.ValidateUtil;
import cn.scw.common.utils.spring.SpringUtils;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;

/**
 *tcp服务管理
 */
@Component
public class TcpServerManager {

    private Logger logger= LoggerFactory.getLogger(this.getClass());

    //获取单例
    public static TcpServerManager getManager(){
        return SpringUtils.getBean(TcpServerManager.class);
    }

    //tcp监听的端口号
    @Value("${tcpserver.port}")
    private int port;

    public Map<String, ChannelHandle> getAllTcpHandles() {
        return allTcpHandles;
    }

    //未验证的通道实例，由于为验证，并不知道连接方信息，只能以channelId为key
    private Map<String,ChannelHandle> allTcpHandles =new HashMap<>();
    //已验证的通道实例，由于已验证，产品编号为可以
    private Map<Integer,ChannelHandle> authedTcpHandles =new HashMap<>();


    /**
     * 根据ChannelId获取ChannelHandle
     * @param channelId 要查找的ChannelId
     * @return 如果未找到，则返回null
     */
    public ChannelHandle getChannelHandleByChannelId(String channelId){
        if(!ValidateUtil.strNotEmptyWithTrim(channelId,null,null)){
            return null;
        }

        return allTcpHandles.get(channelId);
    }

    /**
     * 获取验证过的ChannelHandle
     * @param sensorCode 产品编号
     * @return 如果未找到则返回null
     */
    public ChannelHandle getAuthedChannelHandleBySensorCode(Integer sensorCode){
        if(!ValidateUtil.notNull(sensorCode,null,null)){
            return null;
        }

        return authedTcpHandles.get(sensorCode);
    }

    /**
     * 建立tcp连接时，向管理器注册tcp通道
     * @param channel tcp通道
     */
    public void registTcpChannel(Channel channel){

        if(channel==null||!channel.isActive()){
            throw new RuntimeException("tcp通道不可用");
        }
        //如果不存在，则将新的内容添加入集合中
        String key=channel.id().asLongText();
        if(!allTcpHandles.containsKey(key)){
            ChannelHandle newHandle=this.createHandle(channel);
            allTcpHandles.put(key,newHandle);
        }
    }

    /**
     * 将为验证的通道设置为已验证，并移动到已验证的集合
     * @param sensorCode 产品编号，已验证的通道集合的主键
     * @param channelHandle 连接句柄
     */
    public void authTcpChannel(Integer sensorCode,ChannelHandle channelHandle){

        ValidateUtil.notNull(channelHandle.getChannel(),RuntimeException.class,"连接通道不可为空");

        channelHandle.setIsAuthed(true);
        channelHandle.setSensorCode(sensorCode);

        this.authedTcpHandles.put(sensorCode,channelHandle);

    }

    /**
     * 从集合中移除对应的通道
     * @param channel 被移除的通道
     */
    public void unRegistTcpChannel(Channel channel) {
        try {

            if (channel == null) {
                return;
            }

            //获取通道id
            String key = channel.id().asLongText();
            //关闭通道
            channel.close();

            //从全局集合中移除句柄
            ChannelHandle handle = this.allTcpHandles.get(key);
            this.allTcpHandles.remove(key);

            if (handle != null //handle不为空
                    && handle.getIsAuthed() //通道经过 验证
                    && ValidateUtil.notNull(handle.getSensorCode(), null, null)) {  //通道中产品编码存在

                ChannelHandle authedChannel = this.authedTcpHandles.get(handle.getSensorCode());
                //验证过的通道与未验证过的通道不一定是同一个通道，需要确认关闭
                if (authedChannel != null && authedChannel.getChannel() != null) {
                    authedChannel.getChannel().close();
                }

                //移除已验证集合中的句柄
                this.authedTcpHandles.remove(handle.getSensorCode());

            }
        } catch (Exception ex) {
           logger.info("注销通道异常:"+ex.getMessage());
        }

    }

    /**
     * 用来构造channelHandle实例
     *
     * 仅能构造未验证的实例
     * 该构造并不对入参做校验，需要自行校验进行构造。
     * @param channel 通道实例
     * @return 组装好的ChannelHandle实例
     */
    private ChannelHandle createHandle(Channel channel){
        ChannelHandle handle=new ChannelHandle();
        handle.setIsAuthed(false);
        handle.setStartTimestamp(Calendar.getInstance().getTimeInMillis());
        handle.setChannel(channel);

        return handle;
    }

    /**
     * 启动服务
     */
    public void start(){
        ServiceThread serviceThread=new ServiceThread();
        serviceThread.start();
    }
    /**
     * tcp netty
     */
    private class ServiceThread extends Thread{
        @Override
        public void run(){
            logger.info("开始启动tcp服务");
            EventLoopGroup bossGroup = new NioEventLoopGroup();
            //workerGroup作为worker，处理boss接收的连接的流量和将接收的连接注册进入这个worker
            EventLoopGroup workerGroup = new NioEventLoopGroup();
            try {
                //ServerBootstrap负责建立服务端
                //你可以直接使用Channel去建立服务端，但是大多数情况下你无需做这种乏味的事情
                ServerBootstrap b = new ServerBootstrap();
                b.group(bossGroup, workerGroup)
                        //指定使用NioServerSocketChannel产生一个Channel用来接收连接
                        .channel(NioServerSocketChannel.class)
                        //ChannelInitializer用于配置一个新的Channel
                        //用于向你的Channel当中添加ChannelInboundHandler的实现
                        .childHandler(new ChannelInitializer() {
                                          @Override
                                          protected void initChannel(Channel channel) throws Exception {
                                              //组装自定义协议包字节
                                              channel.pipeline().addLast(new ProtoBytePack());
                                              //处理自定义协议包内容
                                              channel.pipeline().addLast(new ProtoDataHandler());
                                          }
                                      }
                        )
                        //对Channel进行一些配置
                        //注意以下是socket的标准参数
                        //BACKLOG用于构造服务端套接字ServerSocket对象，标识当服务器请求处理线程全满时，用于临时存放已完成三次握手的请求的队列的最大长度。如果未设置或所设置的值小于1，Java将使用默认值50。
                        //Option是为了NioServerSocketChannel设置的，用来接收传入连接的
                        .option(ChannelOption.SO_BACKLOG, 1024)
                        //.option(ChannelOption.SO_SNDBUF, 32*1024) //设置发送缓冲大小
                        //.option(ChannelOption.SO_RCVBUF, 32*1024) //设置接收缓冲大小
                        //是否启用心跳保活机制。在双方TCP套接字建立连接后（即都进入ESTABLISHED状态）并且在两个小时左右上层没有任何数据传输的情况下，这套机制才会被激活。
                        //childOption是用来给父级ServerChannel之下的Channels设置参数的
                        .childOption(ChannelOption.SO_KEEPALIVE, true);
                // Bind and start to accept incoming connections.
                ChannelFuture f = b.bind(port).sync();

                //PushService.run();

                //sync()会同步等待连接操作结果，用户线程将在此wait()，直到连接操作完成之后，线程被notify(),用户代码继续执行
                //closeFuture()当Channel关闭时返回一个ChannelFuture,用于链路检测
                f.channel().closeFuture().sync();
            } catch (Exception ex) {
                System.out.println(ex.getMessage());
            } finally {
                //资源优雅释放
                bossGroup.shutdownGracefully();
                workerGroup.shutdownGracefully();
            }
        }
    }

}
